﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using TiposComuns.Objectos;
using TiposComuns.Excepcoes;
using Servidor.Objectos;
using Servidor.Objectos.Pesquisa;

namespace Servidor.Objectos
{
    /// <summary>
    /// Class that concentrates the data of the server,
    /// this is, the user's state and the logfile.
    /// </summary>
    [Serializable]
    public class PadiBook
    {
        #region Attributes
        private State state = new State();            /*State of the server in terms
                                         * of his knowledge about the other servers
                                         * and the last message received*/
        private User user = new User();              //User's state
        private Logfile logfile = new Logfile();        //Log used to synchronize servers

        private Pendents pendents = new Pendents();     //Log of remote pendent actions
       
        
        /// <summary>
        /// Identifier of the server
        /// </summary>
        public int ID
        {
            get;
            set;
        }

        public State State
        {
            get { return state; }
            set { state = value; }
        }

        public User User
        {
            get { return user; }
            set { user = value; }
        }
        
        public Logfile LogFile
        {
            get { return logfile; }
            set { logfile = value; }
        }

        public Pendents Pendents
        {
            get { return pendents; }
            set { pendents = value; }
        }
        #endregion

        #region Constructors
        public PadiBook()
        {
            this.user = new User();
            this.state = new State();
            this.pendents = new Pendents();
            this.logfile = new Logfile();
        }
        #endregion

        #region Methods

        #region Initializations

        /// <summary>
        /// Loads the data of the user
        /// </summary>
        /// <param name="username">User's username and address of the servers</param>
        public static PadiBook Load(Person person, int id)
        {
            PadiBook pb;
            TextReader tr = null;
            try
            {
                tr = new StreamReader(@person.Username+id + ".xml");
                XmlSerializer xs = new XmlSerializer(typeof(PadiBook));
                pb = (PadiBook)xs.Deserialize(tr);
                tr.Close();
                pb.user.Person = person;
                /* debug */
                /*List<int> initview = new List<int>(3);
                initview.Add(id+1);
                initview.Add(id);
                pb.state.View.ViewState = initview;*/
                /**/
                Console.WriteLine("Servidor inicia com o id "+pb.state.IdMensagem);               
            }
            catch (Exception e)
            {
                //throw e;
                if (tr != null)
                    tr.Close();

                pb = new PadiBook();
                Profile prof = new Profile();
                prof.Username = person.Username;
                pb.User = new User(new Friends(), new Wall(), prof, person);
                pb.ID = id;
                 //debug -> acrescentar amigo 
                /*pb.FriendReceive(new IDMsg(0,true),new Person("ze manel",new Address("192.168.100.1",1040)));
                pb.FriendAccept(new IDMsg(1,true),new Person("ze manel",new Address("192.168.100.1",1040)));
                pb.FriendReceive(new IDMsg(2,true),new Person("xavier",new Address("192.168.100.2",1050)));
                pb.FriendRequest(new IDMsg(3, true), new Person("antonio", new Address("192.169.100.2", 1060)));
                *//*----*/
                List<int> initview = new List<int>(3);
                initview.Add(id);
                pb.state.View.ViewState = initview;
            }

            return pb;
        }

        /// <summary>
        /// Persists the data
        /// </summary>
        public void Save()
        {
            lock (this)
            {
                TextWriter tw = null;
                try
                {
                    tw = new StreamWriter(this.user.Person.Username + this.ID + ".xml");
                    XmlSerializer xs = new XmlSerializer(typeof(PadiBook));
                    xs.Serialize(tw, this);
                    tw.Close();
                }
                catch (Exception e)
                {
                    Console.WriteLine("Erro no save: " + e.Message);
                }
                finally
                {
                    if (tw != null)
                    {
                        tw.Close();
                    }
                }
            }
        }


        /// <summary>
        /// Connects a user to the server.
        /// </summary>
        /// <param name="address">The address of the person and its username</param>
        /// <param name="profile">User's profile</param>
        /// <exception cref="BindException">Throws an exception in case the server isn't bound to the user</exception>
        public State Connect(string username)
        {
            if (user == null || !username.Equals(user.Person.Username))
            {
                throw new BindException(String.Format("Servidor não está associado ao utilizador " + username));
            }

            return state;
        }        

        #endregion

        #region Posts
        public List<Post> PostsSince(int id)
        {
            return this.user.PostsSince(id);
        }

        public List<Post> GetPosts()
        {
            return this.user.GetPosts();
        }

        public void Post(Post post)
        {
            this.user.PostMessage(post);
            this.logfile.Register(new PostAction(post.ID,post));
        }

        public Dictionary<string, IDMsg> GetLastReceivedPosts()
        {
            return this.user.GetLastReceivedPosts();
        }

        public void PostAll(List<Post> posts)
        {
            this.user.PostAll(posts);
            IDMsg id = this.state.IdMensagem.Clone();
            id.ID -= posts.Count;
            foreach (Post p in posts)
            {
                this.logfile.Register(new PostAction(id.Clone(), p));
                id.Inc();
            }
        }
        #endregion

        #region Friends

        public bool MayAddFriend(Person friend)
        {
            return this.user.MayAddFriend(friend);
        }
        /// <summary>
        /// Gets all the friends
        /// </summary>
        /// <returns>Friends</returns>
        public List<Person> GetFriends()
        {
            return this.User.GetFriends();
        }

        public List<Person> GetFriendsReceived()
        {
            return this.User.GetFriendsReceived();
        }

        public List<Person> GetFriendsSent()
        {
            return this.User.GetFriendsSent();
        }

        /// <summary>
        /// Register friend request
        /// </summary>
        /// <param name="id">Message id</param>
        /// <param name="friend">Target of the request</param>
        public void FriendRequest(Person friend)
        {
            /* é pedido externo, portanto tem de ser feito ao primario e ele é que actualiza 
             * o id da msg e manda essa info para o clietne*/
            this.user.RequestFriend(friend);
            IDMsg id = this.state.IdMensagem.Clone();
            id.ID--;

            this.logfile.Register(new RequestFriendAction(id, friend));
        }

        /// <summary>
        /// Receive a request from a friend
        /// </summary>
        /// <param name="id">Id of the message</param>
        /// <param name="friend">Source of the request</param>
        public void FriendReceive(Person friend)
        {
            this.user.ReceiveFriend(friend);
            IDMsg id = this.state.IdMensagem.Clone();
            id.ID--;
            this.logfile.Register(new ReceiveRequestAction(id, friend));
        }

        /// <summary>
        /// Accepts a friend
        /// </summary>
        /// <param name="id">Id of the message</param>
        /// <param name="friend">New friend</param>
        public void FriendAccept(NewFriendPost nfp)
        {
            this.user.AcceptFriend(nfp);
            IDMsg id = this.state.IdMensagem.Clone();
            id.ID--;
            this.logfile.Register(new AcceptFriendAction(id, nfp));
        }

        /// <summary>
        /// Rejects a friend request
        /// </summary>
        /// <param name="id">Message id</param>
        /// <param name="friend">Rejected friend</param>
        public void FriendReject(Person friend)
        {
            this.user.RejectFriend(friend);
            IDMsg id = this.state.IdMensagem.Clone();
            id.ID--;
            this.logfile.Register(new RejectRequestAction(id,friend));
        }

        /// <summary>
        /// Removes a friend 
        /// </summary>
        /// <param name="id">Message id</param>
        /// <param name="friend">Removed friend</param>
        public void FriendRemove(Person target)
        {
            this.user.RemoveFriend(target);
            IDMsg id = this.state.IdMensagem.Clone();
            id.ID--;
            this.logfile.Register(new RemoveFriendAction(id, target));
        }

        #endregion

        #region Profile
        public void SetProfile(Profile profile)
        {
            this.User.Perfil = profile;
            IDMsg id = this.state.IdMensagem.Clone();
            id.ID--;
            this.logfile.Register(new UpdateProfileAction(id, profile, this.user.Perfil));
        }
        #endregion

        #region LogFile
        public List<Action> GetActionsSince(IDMsg lastid)
        {
            return this.logfile.GetActionsSince(lastid);
        }

        public void Synchronize(List<Action> list)
        {
            this.logfile.Synchronize(this.user, list);
        }

        public void RollBack(IDMsg id)
        {
            SerializableDictionary<IDMsg, Action> dic = new SerializableDictionary<IDMsg, Action>();

            foreach (Action act in this.logfile.Actions.Values)
            {
                if (act.Id.ID < id.ID)
                {
                    dic.Add(act.Id, act);
                }
                else
                {
                    act.RollBack(this.user);
                }
            }

            this.logfile.Actions = dic;
            this.state.IdMensagem = id.Clone();
        }
        #endregion

        #endregion               
    }
}
